package com.yst.webplugin.permission;

import com.yst.spring.context.SpringContext;
import com.yst.web.utils.ClassUtils;
import com.yst.web.utils.ThreadUtil;
import com.yst.webplugin.exception.ExceptionPermissionInsufficient;
import com.yst.webplugin.loginplugin.UserUtils;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public abstract class PermissionUtils {

    public interface IOnReady {
        void onReady(List<PermissionItem> permissions);
    }

    private static final List<IOnReady> onReadyListeners=new ArrayList<>();
    public static void addListener(IOnReady listener){
        if (!onReadyListeners.contains(listener)) {
            onReadyListeners.add(listener);
        }
        if (!allPermissionList.isEmpty()){
            listener.onReady(allPermissionList);
        }
    }

    public static void removeListener(IOnReady listener){
        onReadyListeners.remove(listener);
    }

    private static void doOnReady(){
        onReadyListeners.forEach((listener)->{
            try{
                listener.onReady(allPermissionList);
            }
            catch (Exception e){
                e.printStackTrace();
            }
        });
    }

    private static final Map<Field,Boolean> needCheckFieldCache=new ConcurrentHashMap<>();

    private static final Map<Method,Boolean> needCheckMethodCache=new ConcurrentHashMap<>();

    private static boolean needCheckPermission(Field field){
        return needCheckFieldCache.computeIfAbsent(field,(key)->{
            Permission permission=field.getAnnotation(Permission.class);
            if (Objects.nonNull(permission)) return true;
            Permission.Group group=field.getAnnotation(Permission.Group.class);
            if (Objects.nonNull(group)) return true;
            Role role=field.getAnnotation(Role.class);
            if (Objects.nonNull(role)) return true;
            Role.Roles roles=field.getAnnotation(Role.Roles.class);
            if (Objects.nonNull(roles)) return true;
            return false;
        });
    }

    private static boolean needCheckPermission(Method method){
        return needCheckMethodCache.computeIfAbsent(method,(key)->{
            Permission permission=method.getAnnotation(Permission.class);
            if (Objects.nonNull(permission)) return true;
            Permission.Group group=method.getAnnotation(Permission.Group.class);
            if (Objects.nonNull(group)) return true;
            Role role=method.getAnnotation(Role.class);
            if (Objects.nonNull(role)) return true;
            Role.Roles roles=method.getAnnotation(Role.Roles.class);
            if (Objects.nonNull(roles)) return true;
            return false;
        });
    }

    private static final Map<Field,List<String>> fieldPermissions=new ConcurrentHashMap<>();
    private static final Map<Class<?>,List<String>> classPermissions=new ConcurrentHashMap<>();
    private static final Map<Method,List<String>> methodPermissions=new ConcurrentHashMap<>();

    public static List<String> getPermissions(Field field){
        return fieldPermissions.computeIfAbsent(field,(f)->{
            List<String> permissions=new ArrayList<>();
            List<String> clazzPermissions=getPermissions(field.getDeclaringClass());
            Permission annotationPermission=field.getAnnotation(Permission.class);
            List<Permission> annotations=new ArrayList<>();
            if (Objects.nonNull(annotationPermission)) {
                annotations.add(annotationPermission);
            }
            Permission.Group group=field.getAnnotation(Permission.Group.class);
            if (Objects.nonNull(group)) {
                annotations.addAll(Arrays.asList(group.value()));
            }

            for (Permission permission:annotations) {
                if (StringUtils.isEmpty(permission.path())) {
                    clazzPermissions.forEach((p) -> {
                        permissions.add(p.concat(":").concat(permission.value().name));
                    });
                } else {
                    permissions.add(permission.path().concat(":").concat(permission.value().name));
                }
            }
            return permissions;
        });
    }
    public static List<String> getPermissions(Class<?> clazz){
        return classPermissions.computeIfAbsent(clazz,(c)->{
            Permission annotationPermission=clazz.getAnnotation(Permission.class);
            List<Permission> annotations=new ArrayList<>();
            if (Objects.nonNull(annotationPermission)) {
                annotations.add(annotationPermission);
            }
            Permission.Group group=clazz.getAnnotation(Permission.Group.class);
            if (Objects.nonNull(group)) {
                annotations.addAll(Arrays.asList(group.value()));
            }

            return annotations
                    .stream()
                    .map(Permission::path)
                    .collect(Collectors.toList());
        });
    }
    public static List<String> getPermissions(Method method){
        return methodPermissions.computeIfAbsent(method,(m)->{
            List<String> permissions=new ArrayList<>();
            List<String> clazzPermissions=getPermissions(method.getDeclaringClass());
            Permission annotationPermission=method.getAnnotation(Permission.class);
            List<Permission> annotations=new ArrayList<>();
            if (Objects.nonNull(annotationPermission)) {
                annotations.add(annotationPermission);
            }
            Permission.Group group=method.getAnnotation(Permission.Group.class);
            if (Objects.nonNull(group)) {
                annotations.addAll(Arrays.asList(group.value()));
            }

            for (Permission permission:annotations) {
                if (StringUtils.isEmpty(permission.path())) {
                    clazzPermissions.forEach((p) -> {
                        permissions.add(p.concat(":").concat(permission.value().name));
                    });
                } else {
                    permissions.add(permission.path().concat(":").concat(permission.value().name));
                }
            }
            return permissions;
        });
    }
    private static boolean checkPermission(Field field, Collection<String> userPermissions){
        List<String> needPermissions=getPermissions(field);
        return userPermissions.containsAll(needPermissions);
    }
    private static boolean checkPermission(Method method, Collection<String> userPermissions){
        List<String> needPermissions=getPermissions(method);
        return userPermissions.containsAll(needPermissions);
    }

    /**
     * check current user contains permission to call specific field method
     * @param field field
     */
    public static void checkPermission(Field field){

        if (!needCheckPermission(field)) return;

        IUserPermissionProvider userPermissionProvider= SpringContext.getContext()
                .getBean(IUserPermissionProvider.class);
        List<String> userPermissions=userPermissionProvider.getPermissions(UserUtils.getCurrentUserId());
        if (Objects.nonNull(userPermissions)) {
            if (checkPermission(field, userPermissionProvider.getPermissions(UserUtils.getCurrentUserId()))) {
                return;
            }
        }

        List<String> userRoleList= userPermissionProvider.getRoles(UserUtils.getCurrentUserId());

        if (Objects.nonNull(userRoleList)) {
            Set<String> rolePermission = new HashSet<>();
            for (String role : userRoleList) {
                rolePermission.addAll(userPermissionProvider.getRolePermissions(role));
            }
            if (checkPermission(field, rolePermission)) {
                return;
            }
        }

        throw new ExceptionPermissionInsufficient();
    }

    /**
     * check current user contains permission to call specific method
     * @param method method
     */
    public static void checkPermission(Method method){

        if (!needCheckPermission(method)) return;

        IUserPermissionProvider userPermissionProvider= SpringContext.getContext()
                .getBean(IUserPermissionProvider.class);
        List<String> userPermissions=userPermissionProvider.getPermissions(UserUtils.getCurrentUserId());
        if (Objects.nonNull(userPermissions)) {
            if (checkPermission(method, userPermissionProvider.getPermissions(UserUtils.getCurrentUserId()))) {
                return;
            }
        }
        List<String> userRoleList= userPermissionProvider.getRoles(UserUtils.getCurrentUserId());
        Set<String> rolePermission=new HashSet<>();
        if (Objects.nonNull(userRoleList)) {
            for (String role : userRoleList) {
                rolePermission.addAll(userPermissionProvider.getRolePermissions(role));
            }
        }
        if (checkPermission(method,rolePermission)){
            return;
        }

        throw new ExceptionPermissionInsufficient();
    }

    private static final List<PermissionItem> allPermissionList=new ArrayList<>();
    static {
        SpringContext.addOnReadyListener(context -> {
            context.getBeansWithAnnotation(Permission.class)
                    .forEach(((s, object) -> {
                        String permissionStr;
                        Permission permission= object.getClass().getAnnotation(Permission.class);
                        if (Objects.nonNull(permission)){
                            permissionStr=permission.path();
                            allPermissionList.add(new PermissionItem(permissionStr,permission.description()));
                        } else {
                            permissionStr = "";
                        }
                        Map<String,Field> fieldMap=ClassUtils.getClassFields(object.getClass());
                        fieldMap.forEach((fileName,field)->{
                            Permission annotation=field.getAnnotation(Permission.class);
                            if (Objects.nonNull(annotation)){
                                String fullPath=permissionStr.concat(":").concat(annotation.path())
                                        .concat(":").concat(annotation.value().name);
                                List<String> strList=Arrays.asList(fullPath.split(":"));
                                strList=strList.stream().filter((str)->Objects.nonNull(str)&&StringUtils.isNotEmpty(str))
                                        .collect(Collectors.toList());
                                fullPath=String.join(":",strList);
                                allPermissionList.add(
                                        new PermissionItem(fullPath,StringUtils.isEmpty(annotation.description())?
                                                annotation.value().description:annotation.description())
                                );
                            }
                        });
                        for(Method method:object.getClass().getDeclaredMethods()){
                            Permission annotation=method.getAnnotation(Permission.class);
                            if (Objects.nonNull(annotation)){
                                String fullPath=permissionStr.concat(":").concat(annotation.path())
                                        .concat(":").concat(annotation.value().name);
                                List<String> strList=Arrays.asList(fullPath.split(":"));
                                strList=strList.stream().filter((str)->Objects.nonNull(str)&&StringUtils.isNotEmpty(str))
                                        .collect(Collectors.toList());
                                fullPath=String.join(":",strList);
                                allPermissionList.add(
                                        new PermissionItem(fullPath,StringUtils.isEmpty(annotation.description())?
                                                annotation.value().description:annotation.description())
                                );
                            }
                        }
                    }));
            doOnReady();
        });
    }


}
